package org.subethamail.smtp.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.subethamail.smtp.util.Base64;

/**
 * PlainAuthenticator implements the SASL PLAIN mechanism which authenticates
 * the client using a name - password combination.
 * 
 * @see <a href="http://tools.ietf.org/html/rfc4616">RFC 4616: The PLAIN Simple
 *      Authentication and Security Layer (SASL) Mechanism</a>
 */
public class PlainAuthenticator implements Authenticator {
	private final String user;
	private final String password;
	private final SmartClient smartClient;

	public PlainAuthenticator(SmartClient smartClient, String user,
			String password) {
		this.smartClient = smartClient;
		this.user = user;
		this.password = password;
	}

	@Override
	public void authenticate() throws SMTPException,
			AuthenticationNotSupportedException, IOException {
		checkAuthPlainSupport();

		String initialClientResponse = constructInitialClientResponse();
		smartClient.sendAndCheck("AUTH PLAIN " + initialClientResponse);
	}

	/**
	 * Checks if the server supports this mechanism.
	 * 
	 * @throws AuthenticationNotSupportedException
	 *             if the server does not support this mechanism or
	 *             authentication at all.
	 */
	private void checkAuthPlainSupport()
			throws AuthenticationNotSupportedException {
		String mechanismsString = smartClient.getExtensions().get("AUTH");
		if (mechanismsString == null) {
			throw new AuthenticationNotSupportedException(
					"Cannot authenticate, because the AUTH extension is "
							+ "not supported by the server. Maybe the server expects "
							+ "TLS first");
		}
		Set<String> mechanisms = parseMechanismsList(mechanismsString);
		if (!mechanisms.contains("PLAIN")) {
			throw new AuthenticationNotSupportedException(
					"Cannot authenticate, because the PLAIN mechanism is "
							+ "not supported by the server. Maybe the server expects "
							+ "TLS first");
		}
	}

	/**
	 * Parses the EHLO parameter list of the SMTP AUTH extension keyword.
	 * 
	 * @return the set of SASL mechanism names 
	 */
	private Set<String> parseMechanismsList(String authParameters) {
		String[] mechanisms = authParameters.split(" ");
		return new HashSet<String>(Arrays.asList(mechanisms));
	}

	/**
	 * Creates the base64 encoded SASL PLAIN initial response.
	 */
	private String constructInitialClientResponse() throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream(512);
		out.write(0);
		out.write(user.getBytes("UTF-8"));
		out.write(0);
		out.write(password.getBytes("UTF-8"));
		return Base64.encodeToString(out.toByteArray(), false);
	}
}
